require 'faraday'
require 'faraday_middleware'
require 'gamifier/collection'

module Gamifier
  class Engine
    MODELS = %w{ApiKey Network Activity ActivityDefinition Group Player Reward RewardDefinition Site Track Unit User}

    attr_accessor :connection
    attr_writer :config

    def initialize(opts = {})
      opts.each{|k,v| config[k.to_sym] = v}
      raise ArgumentError, "Please configure a :uri and :key first." unless ok?
      @connection = Faraday.new(:url => uri_to) do |builder|
        builder.use Faraday::Request::Multipart
        builder.use Faraday::Request::UrlEncoded  # convert request params as "www-form-urlencoded"
        builder.use Faraday::Response::Logger, Gamifier.logger
        builder.use Faraday::Adapter::NetHttp     # make http requests with Net::HTTP
        builder.use Faraday::Response::ParseJson, :content_type => /\bjson$/
        builder.options[:timeout]       = Gamifier.config[:timeout]       if Gamifier.config[:timeout]
        builder.options[:read_timeout]  = Gamifier.config[:read_timeout]  if Gamifier.config[:read_timeout]
        builder.options[:open_timeout]  = Gamifier.config[:open_timeout]  if Gamifier.config[:open_timeout]
      end
    end

    def config
      @config ||= Gamifier.config.dup
    end

    def ok?
      if config[:enterprise_key].nil? || config[:enterprise_key].empty?
        !config.values_at(:key, :uri).any?{|k| k.nil? || k.empty?}
      else
        !(config[:uri].nil? || config[:uri].empty?)
      end
    end

    # Sends an HTTP request corresponding to +method+, on +path+, with
    # any options given in +opts+:
    # +query+:: a hash of query parameters to pass with the request
    # +head+:: a hash of HTTP headers to pass with the request
    # +body+:: the body to send with the request. If you pass a Ruby
    #          object, it will be automatically converted into
    #          x-www-form-urlencoded by default.
    def transmit(method, path, opts = {})
      res = connection.send(method) do |req|
        req.url [path, "json"].join("."), (opts[:query] || {})
        (opts[:head] || {}).each do |k,v|
          req.headers[k] = v
        end
        req.body = opts[:body] unless opts[:body].nil?
        Gamifier.logger.debug "#{method.to_s.upcase} #{req.inspect}"
      end
      Gamifier.logger.debug "#{res.inspect}"
      case res.status
      when 204
        true
      when 200...300
        res.body
      when 404
        nil
      when 422
        # Badgeville returns 422 when an entry already exists or is not valid
        [:get, :head].include?(method) ? nil : false
      when 400...500
        raise HTTPClientError, res.body
      when 500...600
        raise HTTPServerError, res.body
      else
        raise HTTPError, res.body
      end
    end

    def uri_to(path = '')
      URI.join(File.join(self.config[:uri], self.config[:key] || '', ''), path)
    end

    MODELS.each do |model|
      underscored = Gamifier.underscore(model)
      require "gamifier/models/#{underscored}"
      klass = Gamifier.const_get(model)
      define_method(klass.path.to_sym) do
        Collection.new(self, klass)
      end
    end
  end
end
